package jwt

import (
	"context"
	"errors"
	"net/http"
	"time"

	jwt "github.com/appleboy/gin-jwt/v2"
	"github.com/gin-gonic/gin"

	"github.com/super-team/start-kit/configs"
	"github.com/super-team/start-kit/models"
	"github.com/super-team/start-kit/tools"
	"github.com/super-team/start-kit/tools/log"
	"github.com/super-team/start-kit/tools/util"
)

type Login struct {
	Username string `form:"username" json:"username" binding:"required"`
	Password string `form:"password" json:"password" binding:"required"`
	UUID     string `form:"uuid" json:"uuid" binding:"required"`
	Code     string `form:"code" json:"code" binding:"required"`
}

var (
	identityKey = "id"
	userInfoKey = "userInfo"
	roleInfoKey = "roleInfo"
)

func Auth() *jwt.GinJWTMiddleware {
	ctx := context.Background()
	cfg := configs.Config.JWT
	// the jwt middleware
	authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{
		Realm:       "test",
		Key:         util.StrToBytes(cfg.Secret),
		Timeout:     time.Minute * time.Duration(cfg.Expire),
		MaxRefresh:  time.Hour * 24 * time.Duration(cfg.MaxRefresh),
		IdentityKey: identityKey,
		PayloadFunc: func(data interface{}) jwt.MapClaims {
			if v, ok := data.(map[string]interface{}); ok {
				u, ok := v["user"].(models.Admin)
				if !ok {
					return jwt.MapClaims{}
				}
				r, ok := v["role"].(models.Role)
				if !ok {
					return jwt.MapClaims{}
				}
				return jwt.MapClaims{
					"user_id":   u.ID,
					"role_id":   r.ID,
					"role_key":  r.RoleKey,
					"account":   u.Account,
					"role_name": r.RoleName,
				}
			}
			return jwt.MapClaims{}
		},
		IdentityHandler: func(c *gin.Context) interface{} {
			claims := jwt.ExtractClaims(c)
			account, ok := claims["account"].(string)
			if !ok {
				return nil
			}
			user, err := models.GetAdminByAccount(ctx, account)
			if err != nil {
				log.Log.Err(err).Send()
				return nil
			}
			if *user.Status == 0 {
				return nil
			}
			r, err := models.GetRoleByID(ctx, int64(*user.RoleID))
			if err != nil {
				log.Log.Err(err).Send()
				return nil
			}
			c.Set(userInfoKey, *user)
			c.Set(roleInfoKey, *r)
			return map[string]interface{}{
				"user": *user,
				"role": *r,
			}
		},
		Authenticator: func(c *gin.Context) (interface{}, error) {
			var loginVals Login
			if err := c.ShouldBind(&loginVals); err != nil {
				return "", jwt.ErrMissingLoginValues
			}
			if !tools.CaptchaStore.Verify(loginVals.UUID, loginVals.Code, true) && !configs.Config.Server.Debug {
				return nil, errors.New("验证码错误，请重新输入")
			}
			user, err := models.GetAdminByAccount(ctx, loginVals.Username)
			if err != nil || user == nil || !util.PasswordVerify(loginVals.Password, *user.Password) {
				return nil, errors.New("用户名或密码错误")
			}
			if *user.Status == 0 {
				return nil, errors.New("该账户已被停用，如需登录请联系管理员")
			}
			user.Password = nil
			r, err := models.GetRoleByID(ctx, int64(*user.RoleID))
			if err != nil {
				log.Log.Err(err).Send()
				return nil, errors.New("网络异常，请稍后再试")
			}
			return map[string]interface{}{"user": *user, "role": *r}, nil
		},
		Authorizator: func(data interface{}, c *gin.Context) bool {
			if v, ok := data.(map[string]interface{}); ok {
				u, ok := v["user"].(models.Admin)
				if !ok {
					return false
				}
				r, ok := v["role"].(models.Role)
				if !ok {
					return false
				}
				c.Set(userInfoKey, u)
				c.Set(roleInfoKey, r)
				return true
			}
			return false
		},
		Unauthorized: func(c *gin.Context, code int, message string) {
			c.JSON(http.StatusOK, gin.H{
				"code": code,
				"msg":  message,
				"data": nil,
			})
		},
		LoginResponse: func(c *gin.Context, code int, token string, expire time.Time) {
			c.JSON(http.StatusOK, gin.H{
				"code": http.StatusOK,
				"msg":  "登录成功",
				"data": map[string]interface{}{
					"token":  "Bearer " + token,
					"expire": expire.Unix(),
				},
			})
		},
		LogoutResponse: func(c *gin.Context, i int) {
			c.JSON(http.StatusOK, gin.H{
				"code": http.StatusOK,
				"msg":  "退出登录成功",
				"data": nil,
			})
		},
		RefreshResponse: func(ctx *gin.Context, code int, token string, expire time.Time) {
			ctx.JSON(http.StatusOK, gin.H{
				"code": http.StatusOK,
				"msg":  "刷新token成功",
				"data": map[string]interface{}{
					"token":  "Bearer " + token,
					"expire": expire.Unix(),
				},
			})
		},
		// TokenLookup is a string in the form of "<source>:<name>" that is used
		// to extract token from the request.
		// Optional. Default value "header:Authorization".
		// Possible values:
		// - "header:<name>"
		// - "query:<name>"
		// - "cookie:<name>"
		// - "param:<name>"
		TokenLookup: "header: Authorization, query: token, cookie: jwt",
		// TokenLookup: "query:token",
		// TokenLookup: "cookie:token",

		// TokenHeadName is a string in the header. Default value is "Bearer"
		TokenHeadName: "Bearer",

		// TimeFunc provides the current time. You can override it to use another time value. This is useful for testing or if your server uses a different time zone than your tokens.
		TimeFunc: time.Now,
	})
	if err != nil {
		panic(err)
	}
	return authMiddleware
}

func GetAuthUser(c *gin.Context) *models.Admin {
	user, ok := c.Get(userInfoKey)

	if !ok {
		return nil
	}

	jwtUser, ok := user.(models.Admin)

	if !ok {
		return nil
	}

	return &jwtUser
}

func GetAuthRole(c *gin.Context) *models.Role {
	role, ok := c.Get(roleInfoKey)

	if !ok {
		return nil
	}

	jwtRole, ok := role.(models.Role)

	if !ok {
		return nil
	}

	return &jwtRole
}
